﻿using Microsoft.Crm.Sdk.Samples;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Xrm.Sdk.Metadata;
using Microsoft.Xrm.Sdk.Query;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace crm2011iconupgrader
{
    /// <summary>
    /// This application will automatically greyscale entity icons. This can be used when crm 2011 has been upgraded to 2013 and require a quick transformation into 2013 so that the icons look consistant.
    /// 
    /// The tool will automatically backup icons before making the change. You can also restore the icons to it's original state in case of an error. See usage below.
    /// 
    /// Usage:
    ///     To upgrade all icons
    ///         crm2011iconupgrader.exe "disovery url" "domain" "username" "password" "org name"
    /// 
    ///     To restore icons
    ///         crm2011iconupgrader.exe "discovery url" "domain" "username" "password" "org name" --restore "c:\path\to\restore from\"
    /// </summary>
    class Program
    {
        static void Main(string[] args)
        {
            bool restore = args != null && args.Length > 5 && args[5].Equals("--restore", StringComparison.InvariantCultureIgnoreCase);

            Console.WriteLine("Connecting to crm...");

            AuthenticateWithNoHelp conn = new AuthenticateWithNoHelp(args[0], args[1], args[2], args[3], args[4]);
            IOrganizationService sdk = conn.Connect();
            Console.WriteLine("Connected to crm");

            List<Guid> webresourcesToPublish = new List<Guid>();

            if (restore) { webresourcesToPublish = RestoreIconsToOriginal(sdk, args[6]); }
            else { webresourcesToPublish = ConvertAllIcons(sdk); }

            if (webresourcesToPublish.Count > 0)
            {
                Console.WriteLine("Publishing changes...");

                PublishChanges(sdk, webresourcesToPublish);
            }

            Console.WriteLine("Done!");
        }

        private static List<Guid> ConvertAllIcons(IOrganizationService sdk)
        {
            Console.WriteLine("Starting conversion...");

            List<Guid> webresourcesToPublish = new List<Guid>();

            List<string> icons = GetIcons(sdk);

            var resources = GetWebResources(sdk, icons);

            string backupPath = CreateBackupPath();

            resources.ForEach(a =>
            {
                Guid webresourceId = a.GetAttributeValue<Guid>("webresourceid");
                string name = a.GetAttributeValue<string>("name");
                byte[] content = Convert.FromBase64String(a.GetAttributeValue<string>("content"));

                Console.WriteLine("Processing: {0}", name);

                string iconPath = BackupIcon(backupPath, name, content, webresourceId);

                string iconGreyPath = ConvertToGreyScale(iconPath);

                if (File.Exists(iconGreyPath))
                {
                    Console.WriteLine("\tConverted to grey scale");

                    var contentGrey = File.ReadAllBytes(iconGreyPath);

                    UpdateIcon(sdk, webresourceId, Convert.ToBase64String(contentGrey));

                    Console.WriteLine("\tUpdated icon");

                    webresourcesToPublish.Add(webresourceId);
                }
            });

            return webresourcesToPublish;
        }

        private static List<Guid> RestoreIconsToOriginal(IOrganizationService sdk, string restoreFrom)
        {
            Console.WriteLine("Starting restoration...");

            List<Guid> webresourcesToPublish = new List<Guid>();

            var files = Directory.GetFiles(restoreFrom)
                // ignore all converted files
                .Where(a => !Path.GetFileName(a).StartsWith("grey_", StringComparison.CurrentCultureIgnoreCase)).ToList();

            files.ForEach(a =>
            {
                string fileName = Path.GetFileName(a);

                Guid webresourceId = new Guid(fileName.Substring(0, fileName.IndexOf("_")));
                webresourcesToPublish.Add(webresourceId);

                UpdateIcon(sdk, webresourceId, Convert.ToBase64String(File.ReadAllBytes(a)));

                Console.WriteLine("Restored: {0}", webresourceId);
            });

            return webresourcesToPublish;
        }

        #region helpers
        private static string ConvertToGreyScale(string iconPath)
        {
            string iconGreyPath = Path.Combine(Path.GetDirectoryName(iconPath), "grey_" + Path.GetFileName(iconPath));

            try
            {
                var original = new Bitmap(Image.FromFile(iconPath));
                var bw = MakeBlackWhite(original);

                bw.Save(iconGreyPath);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }

            return iconGreyPath;
        }

        private static string BackupIcon(string backupPath, string name, byte[] content, Guid webresourceId)
        {
            string filePath = Path.Combine(backupPath, 
                string.Format("{0}_{1}", webresourceId, Regex.Replace(name, @"[^\w\.@-]", "")));

            File.WriteAllBytes(filePath, content);

            return filePath;
        }

        private static string CreateBackupPath()
        {
            string path = @".\" + DateTime.Now.ToString("yyyyMMdd_hhmmss");
            
            Directory.CreateDirectory(path);

            return path;
        }
        #endregion

        #region crm
        private static void PublishChanges(IOrganizationService sdk, List<Guid> webresourcesToPublish)
        {
            OrganizationRequest request = new OrganizationRequest { RequestName = "PublishXml" };
            request.Parameters = new ParameterCollection();
            request.Parameters.Add(
                new KeyValuePair<string, object>("ParameterXml",
                    string.Format("<importexportxml><webresources>{0}</webresources></importexportxml>",
                        string.Join("", webresourcesToPublish.Select(a => string.Format("<webresource>{0}</webresource>", a)))
                    )));

            sdk.Execute(request);
        }

        private static void UpdateIcon(IOrganizationService sdk, Guid webresourceId, string content)
        {
            Entity resource = new Entity("webresource");
            resource["webresourceid"] = webresourceId;
            resource["content"] = content;

            sdk.Update(resource);
        }

        private static List<Entity> GetWebResources(IOrganizationService sdk, List<string> icons)
        {
            QueryExpression qe = new QueryExpression("webresource");
            qe.ColumnSet = new ColumnSet("webresourceid", "webresourcetype", "name", "displayname", "content");
            qe.Criteria.AddCondition("iscustomizable", ConditionOperator.Equal, true);
            qe.Criteria.AddCondition("name", ConditionOperator.In, icons.Cast<object>().ToArray());
            qe.AddOrder("name", OrderType.Ascending);

            return sdk.RetrieveMultiple(qe).Entities.ToList();
        }

        private static List<string> GetIcons(IOrganizationService sdk)
        {
            var request = new RetrieveAllEntitiesRequest { EntityFilters = EntityFilters.Entity };
            var response = sdk.Execute(request) as RetrieveAllEntitiesResponse;

            List<string> icons = new List<string>();

            response.EntityMetadata.Where(a => a.IsCustomEntity == true)
                .Select(a => new string[] { a.IconLargeName, a.IconMediumName, a.IconSmallName }).ToList()
                .ForEach(a => { icons.AddRange(a); });

            return icons.Where(a => !string.IsNullOrEmpty(a)).Distinct().ToList();
        }
        #endregion

        public static Bitmap MakeBlackWhite(Bitmap original)
        {
            Bitmap updated = new Bitmap(original.Width, original.Height);
            Graphics g = Graphics.FromImage(updated);

            ColorMatrix colorMatrix = new ColorMatrix(
                new float[][] { 
                    new float[] { .1f, .1f, .1f, 0, 0 }, // red
                    new float[] { .8f, .8f, .8f, 0, 0 }, // blue 
                    new float[] { .11f, .11f, .11f, 0, 0 }, // green
                    new float[] { 0, 0, 0, 1, 0 }, 
                    new float[] { 0, 0, 0, 0, 1 } 
                });

            ImageAttributes attributes = new ImageAttributes();
            attributes.SetColorMatrix(colorMatrix);

            g.DrawImage(original, new Rectangle(0, 0, original.Width, original.Height), 0, 0, original.Width, original.Height, GraphicsUnit.Pixel, attributes);

            g.Dispose();

            return updated;
        }
    }
}
